#include "stdafx.h"
#include "test_base.h"

//---------------------------------------------------------------------------
DWORD test_base::go(int processors, int threads_per_processor, bool is_fair)
{
	printf("Testing %s. %u operations%s on %d processors with %d threads per processor, ring is %s.\r\n", test_name().c_str(), _count_to, _force_context_switch? " with context switch" : "", processors, threads_per_processor, is_fair? "on" : "off");

	_ring = std::make_unique<ring>();
	_threads.clear();
	_is_fair = is_fair;
	_loops = _count_to;

	set_process_affinity(processors); // set process to work on all available processes
	
	test_init();

	int threads_count = processors * threads_per_processor;
	
	// create threads and set their affinity
	for(int i = 0; i < threads_count; i++)
	{
		HANDLE hthread = ::CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)static_threads_func, this, CREATE_SUSPENDED, NULL);

		// calculate processor index to set thread's affinity

		if(!::SetThreadAffinityMask(hthread, calc_thread_affinity(threads_per_processor, i)))
		{
			throw std::exception("SetThreadAffinity Failed");
		}

		_threads.push_back(hthread);
	}

	DWORD start_time = ::GetTickCount();

	// start the test
	for(int i = 0; i < threads_count; i++)
	{
		::ResumeThread(_threads[i]);
	}

	// join the threads
	for(int i = 0; i < threads_count; i++)
	{
		::WaitForSingleObject(_threads[i], INFINITE);
	}

	DWORD end_time = ::GetTickCount();

	printf("Test took %u ms\r\n\r\n", end_time - start_time);

	return end_time - start_time;
}
//---------------------------------------------------------------------------
DWORD test_base::static_threads_func(void* instance)
{
	try
	{
		reinterpret_cast<test_base*>(instance)->threads_func();
	}
	catch(...)
	{
		printf("Closing thread: %d\r\n", ::GetCurrentThreadId());
		
		::Sleep(1000);
		ExitProcess(0);
	}

	return 0;
}
//---------------------------------------------------------------------------
void test_base::threads_func()
{
	if(_is_fair)
	{
		_ring->register_to_ring();

		::Sleep(10); // sleep a bit to let all theads resume and register before running
		
		while(_loops >= 0)
		{
			(*_ring)([&]()->void
			{
				do_test(); // call specific test
			});

			InterlockedDecrement(&_loops);
			/*
			if(_loops % 1000 == 0)
			{
				printf("(%d) - %d\r\n", ::GetCurrentThreadId(), _loops);
			}
			*/
		}
	}
	else
	{
		while(_loops >= 0)
		{
			do_test(); // call specific test

			InterlockedDecrement(&_loops);
			
			/*
			if(_loops % 1000 == 0)
			{
				printf("(%d) - %d\r\n", ::GetCurrentThreadId(), _loops);
			}
			*/
		}
	}
}
//---------------------------------------------------------------------------
DWORD_PTR test_base::calc_thread_affinity(int threads_per_processor, int i)
{
	int processor = i / threads_per_processor;

	printf("Set thread %d to processor index %d\r\n", i, processor);

	DWORD_PTR mask = (1 << processor);

	return mask;
}
//---------------------------------------------------------------------------
void test_base::set_process_affinity(int processors)
{
	DWORD processors_count = tools::get_processors_count();

	if(processors_count < (DWORD)processors)
	{
		throw std::exception("requested number of processors doesn't exist");
	}

	DWORD_PTR mask = 0;
	for(DWORD_PTR i=0 ; i<processors_count ; i++)
	{
		mask |= (1i64 << i);
	}

	::SetProcessAffinityMask(::GetCurrentProcess(), mask);
}
//---------------------------------------------------------------------------